/*************************************************************************
 *
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
 * 
 * Copyright 2008 Sun Microsystems, Inc. All rights reserved.
 * 
 * Use is subject to license terms.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy
 * of the License at http://www.apache.org/licenses/LICENSE-2.0. You can also
 * obtain a copy of the License at http://odftoolkit.org/docs/license.txt
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * 
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 ************************************************************************/

using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using AODL.Document.SpreadsheetDocuments;
using AODL.Document.Styles;
using AODL.Document.TextDocuments;

namespace AODL.Document.Content.Tables
{
    /// <summary>
    /// Row represent a row within a table. If the row is part of a table which is
    /// used in a text document, then Cell merging is possible.
    /// </summary>
    public class Row : IContent, IHtml
    {
        #region Delegates

        /// <summary>
        /// RowChanged delegate
        /// </summary>
        public delegate void RowChanged(int rowNumber, int cellCount);

        #endregion

        private CellCollection _cells;

        /// <summary>
        /// Initializes a new instance of the <see cref="Row"/> class.
        /// </summary>
        /// <param name="document">The document.</param>
        /// <param name="node">The node.</param>
        public Row(IDocument document, XElement node)
        {
            Document = document;
            Node = node;

            InitStandards();
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Row"/> class.
        /// </summary>
        /// <param name="table">The table.</param>
        public Row(Table table)
        {
            Table = table;
            Document = table.Document;
            Node = new XElement(Ns.Table + "table-row");
            InitStandards();
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="Row"/> class.
        /// </summary>
        /// <param name="table">The table.</param>
        /// <param name="styleName">Name of the style.</param>
        public Row(Table table, string styleName)
        {
            Table = table;
            Document = table.Document;
            Node = new XElement(Ns.Table + "table-row");
            Node.SetAttributeValue(Ns.Table + "style-name", styleName);
            RowStyle = Document.StyleFactory.Request<RowStyle>(styleName);
            InitStandards();
        }

        /// <summary>
        /// Gets or sets the node.
        /// </summary>
        /// <value>The node.</value>
        public Table Table { get; set; }

        /// <summary>
        /// Gets or sets the row style.
        /// </summary>
        /// <value>The row style.</value>
        public RowStyle RowStyle
        {
            get { return (RowStyle) Style; }
            set
            {
                StyleName = value.StyleName;
                Style = value;
            }
        }

        /// <summary>
        /// Gets or sets the cell collection.
        /// </summary>
        /// <value>The cell collection.</value>
        public CellCollection Cells
        {
            get { return _cells; }
            set { _cells = value; }
        }

        /// <summary>
        /// Gets or sets the cell collection.
        /// </summary>
        /// <value>The cell collection.</value>
        public CellSpanCollection CellSpanCollection { get; set; }

        /// <summary>
        /// Inits the standards.
        /// </summary>
        private void InitStandards()
        {
            Cells = new CellCollection();
            Cells.Removed += CellCollection_Removed;
            Cells.Inserted += CellCollection_Inserted;

            CellSpanCollection = new CellSpanCollection();
            CellSpanCollection.Inserted += CellSpanCollection_Inserted;
            CellSpanCollection.Removed += CellSpanCollection_Removed;
        }

        /// <summary>
        /// Inserts the cell at the given zero based position.
        /// </summary>
        /// <param name="position">The position.</param>
        /// <param name="cell">The cell.</param>
        public void InsertCellAt(int position, Cell cell)
        {
            // Phil Jollans 16-Feb-2008: Largely rewritten
            if (_cells.Count > position)
            {
                // We need to ask Lars how he intended this list to work.
                // Note that Table.Row_OnRowChanged() adds cells to all rows when
                // we add a cell to a single row.
                _cells.RemoveAt(position);
                _cells.Insert(position, cell);
            }
            else
            {
                while (_cells.Count < position)
                {
                    _cells.Add(new Cell(Table.Document));
                }

                _cells.Add(cell);
            }
        }

        /// <summary>
        /// Merge cells. This is only possible if the rows table is part
        /// of a text document.
        /// </summary>
        /// <param name="document">The TextDocument this row belongs to.</param>
        /// <param name="cellStartIndex">Start index of the cell.</param>
        /// <param name="mergeCells">The count of cells to merge incl. the starting cell.</param>
        /// <param name="mergeContent">if set to <c>true</c> [merge content].</param>
        public void MergeCells(TextDocument document, int cellStartIndex, int mergeCells, bool mergeContent)
        {
            Cells[cellStartIndex].ColumnRepeating = mergeCells.ToString();

            if (mergeContent)
            {
                for (int i = cellStartIndex + 1; i < cellStartIndex + mergeCells; i++)
                {
                    foreach (IContent content in Cells[i].Content)
                        Cells[cellStartIndex].Content.Add(content);
                }
            }

            for (int i = cellStartIndex + mergeCells - 1; i > cellStartIndex; i--)
            {
                Cells.RemoveAt(i);
                CellSpanCollection.Add(new CellSpan(this, Document));
            }
        }

        /// <summary>
        /// Gets the index of the cell.
        /// </summary>
        /// <param name="cell">The cell.</param>
        /// <returns>The index of the cell wthin the cell collection. If the
        /// cell isn't part of the collection -1 will be returned.</returns>
        public int GetCellIndex(Cell cell)
        {
            if (cell != null && Cells != null)
            {
                for (int i = 0; i < Cells.Count; i++)
                    if (Cells[i].Equals(cell))
                        return i;
            }

            return -1;
        }

        /// <summary>
        /// Cells the collection_ removed.
        /// </summary>
        /// <param name="index">The index.</param>
        /// <param name="value">The value.</param>
        private static void CellCollection_Removed(int index, object value)
        {
            ((Cell) value).Node.Remove();
        }

        /// <summary>
        /// Cells the collection_ inserted.
        /// </summary>
        /// <param name="index">The index.</param>
        /// <param name="value">The value.</param>
        private void CellCollection_Inserted(int index, object value)
        {
            //Only Spreadsheet documents are automaticaly resized
            //not needed if the file is loaded (right order!);
            if (Document is SpreadsheetDocument
                && !Document.IsLoadedFile)
            {
                if (Node.Elements().Count() == index)
                    Node.Add(((Cell) value).Node);
                else
                {
                    List<XElement> elements = new List<XElement>(Node.Elements());
                    elements.Insert(index + 1, ((Cell) value).Node);
                    Node.ReplaceNodes(elements);
                }
                Row_OnRowChanged(GetRowIndex(), Cells.Count);
            }
            else
                Node.Add(((Cell) value).Node);
        }

        /// <summary>
        /// Cells the span collection_ inserted.
        /// </summary>
        /// <param name="index">The index.</param>
        /// <param name="value">The value.</param>
        private void CellSpanCollection_Inserted(int index, object value)
        {
            Node.Add(((CellSpan) value).Node);
        }

        /// <summary>
        /// Cells the span collection_ removed.
        /// </summary>
        /// <param name="index">The index.</param>
        /// <param name="value">The value.</param>
        private static void CellSpanCollection_Removed(int index, object value)
        {
            ((CellSpan) value).Node.Remove();
        }

        /// <summary>
        /// Gets the index of the row.
        /// </summary>
        /// <returns>The index within the table rowcollection of this row.</returns>
        private int GetRowIndex()
        {
            for (int i = 0; i < Table.Rows.Count; i++)
            {
                if (Table.Rows[i] == this)
                    return i;
            }
            //Maybe this row isn't already added.
            //e.g. this is a new row which will be added
            //to the end of the collection
            // TODO - there is no "Maybe" :) Throw exception if not found
            return Table.Rows.Count;
        }

        #region IContent Member

        private IStyle _style;

        /// <summary>
        /// Gets or sets the node.
        /// </summary>
        /// <value>The node.</value>
        public XElement Node { get; set; }

        /// <summary>
        /// Gets or sets the name of the style.
        /// </summary>
        /// <value>The name of the style.</value>
        public string StyleName
        {
            get { return (string) Node.Attribute(Ns.Table + "style-name"); }
            set { Node.SetAttributeValue(Ns.Table + "style-name", value); }
        }

        /// <summary>
        /// Every object (typeof(IContent)) have to know his document.
        /// </summary>
        /// <value></value>
        public IDocument Document { get; set; }

        /// <summary>
        /// A Style class wich is referenced with the content object.
        /// If no style is available this is null.
        /// </summary>
        /// <value></value>
        public IStyle Style
        {
            get { return _style; }
            set
            {
                StyleName = value.StyleName;
                _style = value;
            }
        }

        /// <summary>
        /// Gets or sets the node.
        /// </summary>
        /// <value>The node.</value>
        XNode IContent.Node
        {
            get { return Node; }
            set { Node = (XElement) value; }
        }

        #endregion

        #region IHtml Member

        /// <summary>
        /// Return the content as Html string
        /// </summary>
        /// <returns>The html string</returns>
        public string GetHtml()
        {
            string html = "<tr ";

            if (((RowStyle) Style).RowProperties != null)
                html += ((RowStyle) Style).RowProperties.GetHtmlStyle();

            html += ">\n";

            foreach (Cell cell in Cells)
                if (cell is IHtml)
                    html += cell.GetHtml() + "\n";

            html += "</tr>";

            return html;
        }

        #endregion

        #region Eventhandling

        /// <summary>
        /// After a row size changed all rows before the changed row
        /// maybe need to be resized. This also belongs to the columns.
        /// </summary>
        /// <param name="rowNumber">The row number.</param>
        /// <param name="cellCount">The cell count.</param>
        private void Row_OnRowChanged(int rowNumber, int cellCount)
        {
            if (Table.ColumnCollection.Count <= cellCount)
            {
                for (int i = 0; i <= cellCount - Table.ColumnCollection.Count; i++)
                {
                    Table.ColumnCollection.Add(new Column(Table, string.Format("col{0}", Table.ColumnCollection.Count)));
                }
            }

            for (int i = 0; i < rowNumber; i++)
            {
                Row row = Table.Rows[i];
                if (row.Cells.Count >= cellCount)
                    continue;
                for (int ii = 0; ii < cellCount - row.Cells.Count; ii++)
                    //for(int ii = cellCount - row.Cells.Count; ii < cellCount; ii++)
                {
                    row.Cells.Add(new Cell(Table.Document));
                }
            }
        }

        #endregion
    }
}

/*
 * $Log: Row.cs,v $
 * Revision 1.4  2008/04/29 15:39:46  mt
 * new copyright header
 *
 * Revision 1.3  2008/04/10 17:33:15  larsbehr
 * - Added several bug fixes mainly for the table handling which are submitted by Phil  Jollans
 *
 * Revision 1.2  2007/04/08 16:51:23  larsbehr
 * - finished master pages and styles for text documents
 * - several bug fixes
 *
 * Revision 1.1  2007/02/25 08:58:36  larsbehr
 * initial checkin, import from Sourceforge.net to OpenOffice.org
 *
 * Revision 1.3  2006/02/16 18:35:41  larsbm
 * - Add FrameBuilder class
 * - TextSequence implementation (Todo loading!)
 * - Free draing postioning via x and y coordinates
 * - Graphic will give access to it's full qualified path
 *   via the GraphicRealPath property
 * - Fixed Bug with CellSpan in Spreadsheetdocuments
 * - Fixed bug graphic of loaded files won't be deleted if they
 *   are removed from the content.
 * - Break-Before property for Paragraph properties for Page Break
 *
 * Revision 1.2  2006/01/29 18:52:14  larsbm
 * - Added support for common styles (style templates in OpenOffice)
 * - Draw TextBox import and export
 * - DrawTextBox html export
 *
 * Revision 1.1  2006/01/29 11:28:22  larsbm
 * - Changes for the new version. 1.2. see next changelog for details
 *
 */